<?php

declare(strict_types=1);

namespace Core;

/**
 * Class Request
 *
 * 一个用于封装和处理 HTTP 请求信息的现代化、安全的类。
 * 它提供了对 GET、POST、SERVER、COOKIE 等超全局变量的面向对象访问方式，
 * 并增加了输入过滤和类型提示，以提高代码的安全性和可维护性。
 *
 * @package Core
 */
class Request
{
    /**
     * @var array<string, mixed> 存储 GET 请求参数
     */
    public array $get;

    /**
     * @var array<string, mixed> 存储 POST 请求参数
     */
    public array $post;

    /**
     * @var array<string, mixed> 存储 SERVER 服务器信息
     */
    public array $server;

    /**
     * @var array<string, string> 存储 COOKIE 信息
     */
    public array $cookies;

    /**
     * @var array<string, string> 存储请求头信息
     */
    public array $headers;

    /**
     * @var string 请求方法 (e.g., 'GET', 'POST')
     */
    public string $method;

    /**
     * @var string 请求的 URI 路径
     */
    public string $uri;

    /**
     * @var string|false 原始请求体内容
     * Note: In PHP 8.0, property types cannot be union types like string|false for user-defined classes.
     * We use @var annotation to document the possible types.
     * The actual runtime type will be string or false.
     */
    public /*string|false*/ $body; // 移除或注释掉 PHP 8.0 不支持的联合类型

    /**
     * 构造函数：初始化请求对象
     *
     * 该方法在实例化时运行，负责从 PHP 的超全局变量中安全地读取所有相关的 HTTP 请求信息
     *
     * 注意：$get 和 $post 使用 FILTER_DEFAULT (即 FILTER_UNSAFE_RAW)，这意味着数据被强制为字符串，
     *      但仍需调用者自行根据具体用途进行进一步的净化（如 HTML 转义、SQL 预处理等）。
     *      $server 和 $cookies 同理。
     */
    public function __construct()
    {
        // 确定请求方法，默认为 GET
        // 从 server 数据中获取，确保 method 是字符串
        $this->method = strtoupper($this->server['REQUEST_METHOD'] ?? 'GET');

        // 使用 filter_input_array 获取超全局变量数据，FILTER_DEFAULT 强制转换为字符串
        // 但不执行实际的数据清理，调用者仍需负责最终的安全处理。
        // 失败时返回 false，我们将其设置为空数组。
        $this->get     = filter_input_array(INPUT_GET, FILTER_DEFAULT, true) ?? [];
        $this->post    = filter_input_array(INPUT_POST, FILTER_DEFAULT, true) ?? [];
        $this->server  = filter_input_array(INPUT_SERVER, FILTER_DEFAULT, true) ?? [];
        $this->cookies = filter_input_array(INPUT_COOKIE, FILTER_DEFAULT, true) ?? [];

        // 解析 URI 路径，失败则设为根路径 '/'
        // parse_url 可能返回 false，但我们只关心 PATH 部分
        $parsedUriPath = parse_url($this->server['REQUEST_URI'] ?? '', PHP_URL_PATH);
        // Ensure it's a string and handle potential false/null from parse_url
        $this->uri = is_string($parsedUriPath) ? $parsedUriPath : '/';

        // 获取原始请求体
        // file_get_contents can return false on failure.
        $this->body = file_get_contents('php://input');

        // getallheaders() 仅在 Apache (mod_php) 环境下可靠，提供备用方案
        if (function_exists('getallheaders')) {
            $this->headers = getallheaders() ?: []; // getallheaders 也可能返回 false
        } else {
            $this->headers = $this->extractHeadersFromServer($this->server);
        }
    }


    /**
     * 从请求中获取一个输入项（优先从 POST, 然后从 GET 获取）
     *
     * 提供了一个可选的 $filter 参数来对获取到的值进行过滤。
     * 默认情况下，如果未指定过滤器，将返回已由构造函数初步处理（强制为字符串）的原始值。
     * 如果需要更严格的过滤（例如整数、邮箱、URL），应显式传递相应的过滤器常量。
     *
     * @param string $key     要检索的键名
     * @param mixed  $default 如果找不到键，则返回的默认值 (Note: mixed is PHP 8.1+, using @param mixed in docblock)
     * @param int    $filter  可选的 PHP 过滤器 ID (如 FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_VALIDATE_INT)
     *                        如果提供，将使用 filter_var 对找到的值进行过滤。
     *                        如果过滤失败（例如，FILTER_VALIDATE_* 类型验证不通过），则返回 $default。
     * @param array|int $options Options for the filter (e.g., flags or specific options).
     * @return mixed 返回找到的值或默认值 (@return mixed is PHP 8.1+, using docblock)
     */
    // public function input(string $key, mixed $default = null, int $filter = FILTER_DEFAULT, mixed $options = null): mixed
    // 替换为 PHP 8.0 兼容版本:
    public function input(string $key, $default = null, int $filter = FILTER_DEFAULT, $options = null)
    {
        // 优先从 POST 中查找，然后是 GET
        $rawValue = $this->post[$key] ?? $this->get[$key] ?? $default;

        // 如果找到了值并且指定了非默认的过滤器，则进行过滤
        if ($rawValue !== $default && $filter !== FILTER_DEFAULT) {
            // filter_var 返回 false 表示过滤失败（尤其对于 FILTER_VALIDATE_*）
            $filteredValue = filter_var($rawValue, $filter, $options);
            return $filteredValue !== false ? $filteredValue : $default; // 过滤失败则返回默认值
        }

        // 如果没有指定过滤器或者使用的是 FILTER_DEFAULT，直接返回原始值或默认值
        return $rawValue;
    }

    /**
     * 检查 HTTP 请求方法是否与给定的方法匹配
     *
     * @param string $method 要检查的 HTTP 方法 (e.g., 'GET', 'POST', 'PUT')
     * @return bool 如果方法匹配则返回 true，否则返回 false
     */
    public function is(string $method): bool
    {
        return strcasecmp($this->method, strtoupper($method)) === 0;
    }

    /**
     * 备用方案：当 getallheaders() 不可用时，从 $_SERVER 中提取请求头
     *
     * @param array<string, mixed> $serverData 已经过滤的 $_SERVER 数组数据
     * @return array<string, string> 格式化后的请求头数组
     */
    private function extractHeadersFromServer(array $serverData): array
    {
        $headers = [];
        foreach ($serverData as $key => $value) {
             // 确保 value 是字符串，因为 header 值应该是字符串
             if (is_string($value) && str_starts_with($key, 'HTTP_')) { // str_starts_with is PHP 8.0+
                // 将 HTTP_CONTENT_TYPE 转换为 Content-Type
                // str_replace('_', '-', ...) 替换下划线为连字符
                // strtolower(...) 统一转为小写以便标准化
                // ucwords(..., '-') 将每个单词首字母大写（以连字符分隔）
                $headerKey = ucwords(strtolower(str_replace('_', '-', substr($key, 5))), '-');
                $headers[$headerKey] = $value; // $value 已经是字符串（来自 filter_input_array）
            }
        }
        return $headers;
    }
}
